/*
 * $Id: popen.C,v 1.6 2001/02/05 13:43:21 dms Exp $
 *
 */

#include <popen/popen.h>
#include <popen/autostr.h>

#ifdef HAVE_SYSLOG_H
# include <syslog.h>
#endif

int Popen::break_up( const char *str, char *argv[], int max_args)
{
  const char *_src, *t, *t1,*t2;
  int fields = 0;
  int lit = 0;
  int len = strlen(str)+1;

  const char *eptr = str+len;

 _src = str;

 while(_src && (_src < eptr))
  { t1 = t2 = _src;
    while(*t1 == ' ' || *t1 == '\t') ++t1; //DMS
    if (*t1 == '\"' || *t1 =='\''){ lit = *t1; ++t1; }
    t = t1;

    while (t < eptr)
     {
       t2 = t;
       if ((*t == ' ' || *t == '\t' || *t == '\n') && *(t-1) != '\\' && !lit) break;
       if (*t == lit && *(t-1) != '\\') { ++t; break;}
       ++t;
     }
	

    if (fields > max_args) break;
    argv[fields] = ( t2-t1 > 0) ? strndup(t1,t2-t1) : 0 ;
    _src = t+1;
   ++ fields;
    lit = 0;
  }

  argv[fields] = 0;
  return fields;
}

/*
int Popen::break_up( char *str, char *argv[], int max_args)
{
  int argc;
  char *cp;

 // break up string into pieces
  for (argc = 0, cp = str;; cp = NULL)
   {
     char *p = strtok(cp, " \t\n");
     if (!p || argc == max_args)
      { argv[argc++] = 0;
        break;
      }

      argv[argc++] = strdup(p);
   }

  return argc;
}
*/

void Popen::clean(char *argv[])
{
   int argc;
   for (argc = 1; argv[argc] != NULL; argc++)
       { free((char *) argv[argc]);
       }
}

FILE *Popen::open(const char *arg_program, const char *arg_env, const char *type)
{
  	if (cleanup_mode != UNINIT)
		throw PopenException("Object already open in '%d' mode", cleanup_mode);
  FILE *iop;
  int  pdes[2];
  char *program = 0, *env = 0;
  char *argv[10], *envp[10];
  argv[0] = 0;

  try
  {
     if ((*type != 'r' && *type != 'w') || type[1])
        throw PopenException("Invalid pipe type '%s'", type );

     if (pipe(pdes) < 0)
        throw PopenException("pipe(2) error (%m)");

     program = strdup(arg_program);

     break_up(program, argv, 10);

     if (arg_env)
        { env = strdup(arg_env);
          break_up(env, envp, 10);
        }

     iop = NULL;

     switch (_child_pid = fork())
     {
        case -1:	/* error */
         ::close(pdes[0]);
         ::close(pdes[1]);
           throw PopenException("fork(2) error (%m)");

       case 0:	/* child */
        if (*type == 'r')
           { if (pdes[1] != 1)
              { dup2(pdes[1], 1);
	        dup2(pdes[1], 2);	/* stderr, too! */
	        ::close(pdes[1]);
              }
            ::close(pdes[0]);
           }
           else
            { if (pdes[0] != 0)
                 { dup2(pdes[0], 0);
	           ::close(pdes[0]);
                 }
            }
        ::close(pdes[1]);

        dup2(1, 2);   // 2>&1 my improvement ;-))
		cleanup_mode = OPEN;
        if (env)
           execve(argv[0], argv, envp);
        else
           execvp(argv[0], argv);

#ifdef HAVE_SYSLOG_H
         syslog(LOG_NDELAY|LOG_ERR|LOG_PID,"Exec error (%m)");
#endif

        _exit(1);

     default: /* parent */
       if (*type == 'r')
          { iop = fdopen(_pipe_fd = pdes[0], type);
            ::close(pdes[1]);
          }
          else
           { iop = fdopen(_pipe_fd = pdes[1], type);
             ::close(pdes[0]);
           }
		  cleanup_mode = OPEN;
    } // switch

   clean(argv);
   free( program);

   if (env)
      {  clean(envp);
         free( env);
      }

   return (iop);
 }
 catch(...)
  {
    clean(argv);
    free( program);

    if (env)
       {  clean(envp);
          free (env);
       }

    throw;
  }

}

FILE *Popen::open_stderr(const char *arg_program, FILE **se, const char *arg_env, const char *type)
{
  	if (cleanup_mode != UNINIT)
		throw PopenException("Object already open in '%d' mode", cleanup_mode);
  FILE *iop;
  int  pdes[2];
  int  pdes_err[2];
  char *program = 0, *env = 0;
  char *argv[10], *envp[10];

  try
  {
     if ((*type != 'r' && *type != 'w') || type[1])
         throw PopenException("Invalid pipe type '%s'", type );

     if (pipe(pdes) < 0)
        throw PopenException("pipe(2) error (%m)");
	
     if (pipe(pdes_err) < 0)
		 throw PopenException("pipe(2) error (%m)");
	

     program = strdup(arg_program);

     break_up(program, argv, 10);

     if (arg_env)
        { env = strdup(arg_env);
          break_up(env, envp, 10);
        }

     iop = NULL;

     switch (_child_pid = fork())
     {
        case -1:	/* error */
         ::close(pdes[0]);
         ::close(pdes[1]);
           throw PopenException("fork(2) error (%m)");

       case 0:	/* child */
        if (*type == 'r')
           {
				if (pdes[1] != 1)
				{
					dup2(pdes[1], 1);
					dup2(pdes_err[1], 2);	/* stderr, too! */
					::close(pdes[1]);
					::close(pdes_err[1]);
				}
				::close(pdes[0]);
				::close(pdes_err[0]);
				
           }
           else
            {
			   if (pdes[0] != 0)
               {
				   dup2(pdes[0], 0);
				   ::close(pdes[0]);
               }
            }
        ::close(pdes[1]);
		cleanup_mode = OPENSTDERR;

//        dup2(2, 1);   // 2>&1 my improvement ;-))
//		if (char* p = strrchr(argv[0], '/'))
//		{
//			char* directory = strndup(argv[0],  p - argv[0]);
//			chdir(directory);
//			free (directory);
//		}
        if (env)
           execve(argv[0], argv, envp);
        else
           execvp(argv[0], argv);
		
		

#ifdef HAVE_SYSLOG_H
         syslog(LOG_NDELAY|LOG_ERR|LOG_PID,"Exec error (%m)");
#endif

        _exit(1);

     default: /* parent */
       if (*type == 'r')
          {
		   FH_1 = *se = fdopen(_pipe_fd_stderr = pdes_err[0], "r");
			::close(pdes_err[1]);
		   FH_2 = iop = fdopen(_pipe_fd = pdes[0], type);
            ::close(pdes[1]);
          }
          else
           { FH_1 = iop = fdopen(_pipe_fd = pdes[1], type);
             ::close(pdes[0]);
           }
		  cleanup_mode = OPENSTDERR;
    } // switch

   clean(argv);
   free (program);

   if (env)
      {  clean(envp);
         free( env);
      }

   return (iop);
 }
 catch(...)
  {
    clean(argv);
	free (program);

    if (env)
       {  clean(envp);
		free( env);
       }

    throw;
  }

}

void Popen::open2(const char *program, const char *env, FILE **sf, FILE **df)
{
  	if (cleanup_mode != UNINIT)
		throw PopenException("Object already open in '%d' mode", cleanup_mode);
  int  pdes_in[2];
  int  pdes_out[2];
  char *argv[10], *envp[10];

  if (pipe(pdes_in) < 0)
     throw PopenException("pipe(2) error (%m)");

  if (pipe(pdes_out) < 0)
     throw PopenException("pipe(2) error (%m)");

   break_up(program, argv, 10);
   if (env)
     break_up(env, envp, 10);

  *sf = 0; *df = 0;
  switch (_child_pid = fork())
   {
     case -1:	/* error */
       ::close(pdes_in[0]);  ::close(pdes_in[1]);
       ::close(pdes_out[0]); ::close(pdes_out[1]);
        throw PopenException("fork(2) error (%m)");

     case 0:	/* child */
          if (pdes_in[1] != 1)
             { dup2(pdes_in[1], 1);
	       dup2(pdes_in[1], 2);	/* stderr, too! */
	       ::close(pdes_in[1]);
              }
            ::close(pdes_in[0]);

           if (pdes_out[0] != 0)
              { dup2(pdes_out[0], 0);
	        ::close(pdes_out[0]);
               }
            ::close(pdes_out[1]);
         dup2(1, 2);   // 2>&1 my improvement ;-))
		 cleanup_mode = OPEN2;
         if (env) execve(argv[0], argv, envp); else execv(argv[0], argv);
         _exit(1);

   default: /* parent */
         FH_1 = *sf = fdopen(_pipe_fd_in = pdes_in[0], "r");
          ::close(pdes_in[1]);
         FH_2 = *df = fdopen(_pipe_fd_out = pdes_out[1], "w");
          ::close(pdes_out[0]);
		  cleanup_mode = OPEN2;

  } // switch

 clean(argv);
 if (env)
  clean(envp);

}

int Popen::close (FILE *iop)
{
  	if (cleanup_mode != OPEN)
		throw PopenException("Object in the wrong mode at cleanup ('%d')", cleanup_mode);
  int status;
  pid_t pid;
  sigset_t omask, mask;

  sigemptyset(&mask);
  sigaddset(&mask, SIGINT);
  sigaddset(&mask, SIGQUIT);
  sigaddset(&mask, SIGHUP);

  /* pclose returns -1 if stream is not associated with a `popened' command,
     or, if already `pclosed'. */
  if ((_child_pid < 0) || (fileno(iop) != _pipe_fd))
    return (-1);

  fclose(iop);
  sigprocmask(SIG_BLOCK, &mask, &omask);

  while ((pid = wait(&status)) != _child_pid && (pid != -1));
  sigprocmask(SIG_SETMASK, &omask, NULL);

  _child_pid = -1;
  _pipe_fd = -1;

#ifdef WEXITSTATUS
  /* this is the fully POSIX compliant implementation */
  return (pid == -1 ? -1 : WEXITSTATUS(status));
#else
  return (pid == -1 ? -1 : status.w_status);
#endif
  cleanup_mode = UNINIT;
}

int Popen::close_stderr (FILE *iop, FILE *se)
{
  	if (cleanup_mode != OPENSTDERR)
		throw PopenException("Object in the wrong mode at cleanup ('%d')", cleanup_mode);
  int status;
  pid_t pid;
  sigset_t omask, mask;

  sigemptyset(&mask);
  sigaddset(&mask, SIGINT);
  sigaddset(&mask, SIGQUIT);
  sigaddset(&mask, SIGHUP);

  /* pclose returns -1 if stream is not associated with a `popened' command,
     or, if already `pclosed'. */
  if ((_child_pid < 0) || (fileno(iop) != _pipe_fd) || (fileno(se) != _pipe_fd_stderr))
    return (-1);

  fclose(iop); fclose(se);
  sigprocmask(SIG_BLOCK, &mask, &omask);

  while ((pid = wait(&status)) != _child_pid && (pid != -1));
  sigprocmask(SIG_SETMASK, &omask, NULL);

  _child_pid = -1;
  _pipe_fd_stderr = -1;
  _pipe_fd = -1;

#ifdef WEXITSTATUS
  /* this is the fully POSIX compliant implementation */
  return (pid == -1 ? -1 : WEXITSTATUS(status));
#else
  return (pid == -1 ? -1 : status.w_status);
#endif
  cleanup_mode = UNINIT;
}

int Popen::close2 (FILE *sf, FILE *df)
{
  	if (cleanup_mode != OPEN2)
		throw PopenException("Object in the wrong mode at cleanup ('%d')", cleanup_mode);
  int status;
  pid_t pid;
  sigset_t omask, mask;

  sigemptyset(&mask);
  sigaddset(&mask, SIGINT);
  sigaddset(&mask, SIGQUIT);
  sigaddset(&mask, SIGHUP);

  /* pclose returns -1 if stream is not associated with a `popened' command,
     or, if already `pclosed'. */
  if ((_child_pid < 0) || (fileno(sf) != _pipe_fd_in) || (fileno(df) != _pipe_fd_out))
    return (-1);

  fclose(sf); fclose(df);
  sigprocmask(SIG_BLOCK, &mask, &omask);

  while ((pid = wait(&status)) != _child_pid && (pid != -1));
  sigprocmask(SIG_SETMASK, &omask, NULL);

  _child_pid = -1;
  _pipe_fd_in = -1;
  _pipe_fd_out = -1;

#ifdef WEXITSTATUS
  /* this is the fully POSIX compliant implementation */
  return (pid == -1 ? -1 : WEXITSTATUS(status));
#else
  return (pid == -1 ? -1 : status.w_status);
#endif
  cleanup_mode = UNINIT;
}

int Popen::close ()
{
  	if (cleanup_mode == UNINIT)
		throw PopenException("Object not opened at cleanup ('%d')", cleanup_mode);
	
	int ret_value;
	switch (cleanup_mode)
	{
		case OPEN:
			if (FH_1 == 0)
				throw PopenException("File handles null at cleanup ('%d')", cleanup_mode);
			ret_value = close (FH_1);
			FH_1 = 0;
			break;
		case OPEN2:
			if (FH_1 == 0 || FH_2 == 0)
				throw PopenException("File handles null at cleanup ('%d')", cleanup_mode);
			ret_value = close2 (FH_1, FH_2);
			break;
		case OPENSTDERR:
			if (FH_1 == 0 || FH_2 == 0)
				throw PopenException("File handles null at cleanup ('%d')", cleanup_mode);
			ret_value = close_stderr (FH_1, FH_2);
			break;
		default:
			throw PopenException("Unknown mode at cleanup ('%d')", cleanup_mode);
	}
	FH_1 = 0;
	FH_2 = 0;
	return ret_value;
}

